package com.hugh.dubbo.filter;

import com.google.common.base.Stopwatch;
import com.hugh.dubbo.data.MonitorData;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.ListenableFilter;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.filter.ActiveLimitFilter;
import org.apache.dubbo.rpc.support.RpcUtils;
import brave.Span;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import static com.hugh.dubbo.data.MonitorData.BAD_STATUS;
import static com.hugh.dubbo.data.MonitorData.HEALTH_CODE;

/**
 * @Author: Hugh
 * @Date: 2020/7/4
 */
@Slf4j
@Activate(group = {CommonConstants.PROVIDER}, value = "tPMonitorFilter", order = 1)
public class TPMonitorFilter extends ListenableFilter {

    private Stopwatch stopwatch;

    private Span.Kind kind;

    private ThreadLocal<String> local = new ThreadLocal<>();

    public TPMonitorFilter() {
        super.listener = new TPMonitorFilter.DubboMonitorListener();
    }
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        // 接口调用开始时间
        String path = getPath(invoker, invocation);
        local.set(path);
        stopwatch = Stopwatch.createStarted();
        return invoker.invoke(invocation);
    }

    private String getPath(Invoker<?> invoker, Invocation invocation) {
        String service = invoker.getInterface().getSimpleName();
        String method = RpcUtils.getMethodName(invocation);
        RpcContext context = RpcContext.getContext();
        String path = local.get();
        if (null == path) {
            try {
                kind = context.isProviderSide() ? Span.Kind.PRODUCER : Span.Kind.CONSUMER;
            } catch (NullPointerException e) {
                kind = Span.Kind.CLIENT;
            }
            if (StringUtils.isNotEmpty(kind.name())) {
                path = kind.name() + "-" + service + "." + method;
            }

        }
        return Optional.ofNullable(path).orElse("");
    }


    class DubboMonitorListener implements Listener {
        @Override
        public void onResponse(Result result, Invoker<?> invoker, Invocation invocation) {
            printLog(invoker, invocation, result, null);
        }

        @Override
        public void onError(Throwable throwable, Invoker<?> invoker, Invocation invocation) {
            printLog(invoker, invocation, null, throwable);
        }


        /**
         * 打印返回数据
         *
         * @param invoker
         * @param invocation
         * @param result
         */
        private void printLog(Invoker<?> invoker, Invocation invocation, Result result, Throwable throwable) {
            int status = BAD_STATUS;
            Throwable error;
            Map<String, String> attachments = null;
            if (null != throwable) {
                error = throwable;
            } else if (result.hasException()) {
                error = result.getException();
            } else {
                error = null;
                status = HEALTH_CODE;
                attachments = result.getAttachments();
            }

//            log.info("接口监控 dubbo rpc MethodName = {}, request Arguments = {}, request Attachments = {}, response exception = {}, response Attachments = {}, latency = {}",
//                    invoker.getInterface().getSimpleName() + invocation.getMethodName(), invocation.getArguments(), invocation.getAttachments(), error, attachments, stopwatch.elapsed(TimeUnit.MILLISECONDS));

            String path = getPath(invoker, invocation);
            onFinish(path, status, stopwatch);
            local.remove();
        }
    }

    /**
     * 统计计时器信息
     * @param path 请求路径
     * @param status 状态
     * @param stopwatch 计时器信息
     */
    private void onFinish(String path, int status, Stopwatch stopwatch) {

        if(BAD_STATUS.equals(status)){
            MonitorData.saveMonitorData(path, -1L);
        }
        if(HEALTH_CODE.equals(status)){
            MonitorData.saveMonitorData(path, stopwatch.elapsed(TimeUnit.MILLISECONDS));
        }
    }
}
